using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

using SR = System.Reflection;
using SRE = System.Reflection.Emit;

using Multiplicity.Runtime.Serialization.Binary;

namespace Multiplicity.Reflection {

    class MetadataParser : BinaryParser {

        MetadataStreamHeader header;

        protected MetadataParser(BufferSegment segment)
            : this(null, segment) {
        }   

        public MetadataParser(MetadataStreamHeader header, BufferSegment segment) 
            : base(segment) {

            this.header = header;
        }

        public MetadataStreamHeader Header {
            get {
                return header;
            }
            protected set {
                header = value;
            }
        }

        public bool TableExists(MetadataTableType table) {
            return header.TableExists(table);
        }

        public uint TableRowCount(MetadataTableType table) {
            return header.TableRowCount(table);
        }

        public uint ReadTableIndex(MetadataTableType table) {
            Throw.IfIsNotDefined(table);

            if (TableRowCount(table) <= UInt16.MaxValue) {
                return ReadUInt16();
            }

            return ReadUInt32();
        }

        public uint ReadHeapIndex(HeapType heap) {
            Throw.IfIsNotDefined(heap);

            byte mask = (byte)(heap);
            if ((header.HeapSizeMask & mask) == mask) {
                return ReadUInt32();
            }

            return ReadUInt16();
        }

        public uint ReadToken(CodedTokenFamilyType family) {
            Throw.IfIsNotDefined(family);

            return CodedToken.ReadToken(this, CodedTokenFamily.GetByType(family));
        }

        public uint ReadCompressedToken(CodedTokenFamilyType family) {
            Throw.IfIsNotDefined(family);

            uint token = CompressedInt.Read(this);
            return CodedToken.DecodeToken(token, CodedTokenFamily.GetByType(family));
        }

        protected override RecordParserBuilder CreateParserBuilder(Type type) {
            return new MetadataParserBuilder(this, type);
        }

    }

    sealed class MetadataParserBuilder : RecordParserBuilder {

        internal MetadataParserBuilder(MetadataParser parser, Type recordType)
            : base(parser, recordType) {
        }

        protected override void EmitRead(SRE.ILGenerator il, SR.FieldInfo field) {

            CodedTokenAttribute token = BinaryParser.GetAttribute<CodedTokenAttribute>(field);
            TableIndexAttribute table = BinaryParser.GetAttribute<TableIndexAttribute>(field);
            HeapIndexAttribute heap = BinaryParser.GetAttribute<HeapIndexAttribute>(field);

            if (token != null) {

                il.Emit(SRE.OpCodes.Ldarg_0);
                il.Emit(SRE.OpCodes.Ldc_I4_S, (byte)token.Family);
                il.EmitCall(SRE.OpCodes.Callvirt, miReadToken, null);

                return;
            }

            if (table != null) {

                il.Emit(SRE.OpCodes.Ldarg_0);
                il.Emit(SRE.OpCodes.Ldc_I4, (int)table.Table);
                il.EmitCall(SRE.OpCodes.Callvirt, miReadTableIndex, null);

                return;
            }

            if (heap != null) {

                il.Emit(SRE.OpCodes.Ldarg_0);
                il.Emit(SRE.OpCodes.Ldc_I4, (int)heap.Heap);
                il.EmitCall(SRE.OpCodes.Callvirt, miReadHeapIndex, null);

                return;
            }

            base.EmitRead(il, field);
        }

        static readonly SR.MethodInfo miReadToken = typeof(MetadataParser).GetMethod("ReadToken");
        static readonly SR.MethodInfo miReadTableIndex = typeof(MetadataParser).GetMethod("ReadTableIndex");
        static readonly SR.MethodInfo miReadHeapIndex = typeof(MetadataParser).GetMethod("ReadHeapIndex");
    }

}
